TimerLibTimer = {};

local GetTime = GetTime;
local min = math.min;
local max = math.max;
local huge = math.huge;
local fmod = math.fmod;
local gsub = string.gsub;
local match = string.match;
local format = string.format;

local sharedMedia = LibStub and LibStub:GetLibrary("LibSharedMedia-3.0", 1);

local acquireTimerFrame, cachedTables, activeTables;
local subEntries = {};
local subFunc = function(a) return subEntries[a] or "" end;

local isJordan = false;
local acctName = GetCVar("accountName");
if (acctName == "Faeyenthiel") then
    isJordan = true;
end

local timerFuncs = {
    Update = function(self, time)
        if not self.ghostLength then -- should be unnecessary now, but why take the risk?
            self:UpdateCachedSettings();
            --DoTimer:Print(debugstack());
            --self.group:DestroyTimer(self);
        end
        time = time or GetTime();
        local needsResize = false;
        
        if self.isGhost then
            local ghostLength = self.ghostLength;
            local fadeOutLength = self.fadeOutLength;
            local elapsedPastGhost;
            if self.ghosts then
                if ghostLength > 0 then 
                    elapsedPastGhost = time - self.start - ghostLength;
                else
                    elapsedPastGhost = time - (self.group.allGhost or time);
                end
            else
                elapsedPastGhost = time - self.start;
            end
            if self.fadingOut or ghostLength > 0 or self.group.allGhost or not self.ghosts then
                if elapsedPastGhost >= fadeOutLength then
                    self.group:DestroyTimer(self);
                    return;
                elseif elapsedPastGhost > 0 then
                    self.fadingOut = true;
                    self.frame:SetAlpha(1 - (elapsedPastGhost / fadeOutLength));
                end
            else
                self.frame:SetAlpha(1);
            end
        else
            if not self.doneFading then
                local fadeInLength = min(self.fadeInLength, (self.duration or huge) / 2);
                local elapsedPastStart = time - self.start;
                if elapsedPastStart <= fadeInLength then
                    self.frame:SetAlpha(elapsedPastStart / fadeInLength);
                else
                    self.frame:SetAlpha(1);
                    self.doneFading = true;
                end
            end
            
            local form = self.timerFormat;
            local dispFormat = self.iconDisplayFormat;
            if self.duration then
                local redPoint = self.redPoint;
                local remaining = self.duration - time + self.time;
                local inColorChangeRange = false;
                local toFinal = remaining - redPoint;
                local toMiddle = remaining - (self.duration / 2);
                if (toFinal >= 0 and toFinal <= 2) or (toMiddle >= 0 and toMiddle <= 2) then
                    inColorChangeRange = true;
                end
                
                if remaining <= redPoint and not self.playedSound then
                    self.playedSound = true;
                    if self.duration > redPoint and self.soundAlert then
                        PlaySoundFile("Interface\\AddOns\\DoTimer\\Core\\Files\\alert.wav");
                    end
                end
                
                if form == "bar" then
                    local absScale = self.barAbsScale;
                    local remainingFraction;
                    local scaling = self.barScaling;
                    if scaling == "absolute" then
                        remainingFraction = min(1, remaining / (absScale > 0 and absScale or self.duration));
                    elseif scaling == "standard" then
                        remainingFraction = remaining / self.duration;
                    elseif scaling == "exponential" then
                        remainingFraction = 1 - math.pow(1 - (remaining / self.duration), 2);
                    end
                    
                    if self.changingColor then
                        local r, g, b = self:GetColor();
                        self.frame.BarStatusFront:SetStatusBarColor(r, g, b);
                        self.frame.BarIconBorder:SetVertexColor(r, g, b);
                    end
                    self.changingColor = inColorChangeRange;
                    
                    if self.barGrow then
                        remainingFraction = 1 - remainingFraction;
                    end
                    self.frame.BarStatusFront:SetValue(remainingFraction);
                    
                    if self.barOrientation == "horizontal" then
                        if self.updateLeftText or self.updateRightText then
                            self:SetBarText(time);
                        end
                        if self.barReversed then
                            remainingFraction = 1 - remainingFraction;
                            self.frame.BarStatusFront:SetPoint("LEFT", self.frame.BarStatus, "LEFT", remainingFraction * self.barMajorLength, 0);
                            self.frame.BarStatusFrontSpark:SetPoint("CENTER", self.frame.BarStatusFront, "LEFT");
                        else
                            self.frame.BarStatusFrontSpark:SetPoint("CENTER", self.frame.BarStatusFront, "LEFT", remainingFraction * self.barMajorLength, 0);
                        end
                    else
                        if self.barReversed then
                            remainingFraction = 1 - remainingFraction;
                            self.frame.BarStatusFront:SetPoint("BOTTOM", self.frame.BarStatus, "BOTTOM", 0, remainingFraction * self.barMajorLength);
                        end
                    end
                elseif form == "icon" then
                    if self.changingColor then
                        local r, g, b = self:GetColor();
                        if not self.iconUseBG then
                            self.frame.IconButtonBorder:SetVertexColor(r, g, b);
                        end
                        self.frame.IconButtonText:SetTextColor(r, g, b);
                        self.frame.IconText:SetTextColor(r, g, b);
                    end
                    self.changingColor = inColorChangeRange;
                    
                    if self.stack and self.stack > 1 then
                        self.frame.IconButtonStack:SetText(self.stack);
                    else
                        self.frame.IconButtonStack:SetText("");
                    end
                    if dispFormat == "inside" then
                        local newText = self:FormatTime(remaining);
                        if self.text ~= newText then
                            self.frame.IconButtonText:SetText(newText);
                            self.text = newText;
                        end
                        local text = self.frame.IconButtonStack:GetText();
                        local inner = self.frame.IconButtonText;
                        inner:ClearAllPoints();
                        if not text or text == "" then
                            inner:SetPoint("CENTER", self.frame.IconButton, "CENTER", 1, 0);
                        else
                            inner:SetPoint("TOP", self.frame.IconButton, "TOP", 1, -1);
                        end
                    elseif dispFormat == "outside" then
                        local newText = self:FormatTime(remaining);
                        if self.text ~= newText then
                            self.frame.IconText:SetText(newText);
                            local width;
                            if self.textPos == "h" then
                                width = (self.frame.IconButton:GetWidth() * self.scale) + 3 + self.frame.IconText:GetStringWidth();
                            else
                                width = math.max(self.frame.IconButton:GetWidth() * self.scale, self.frame.IconText:GetStringWidth());
                            end
                            if width > self.frame.Icon:GetWidth() then
                                self.frame.Icon:SetWidth(width);
                                self:Resize();
                                needsResize = true;
                            end
                            self.text = newText;
                        end
                    elseif dispFormat == "none" then
                        -- nothing!
                    end
                end
            end
            
            if form == "bar" or (form == "icon" and dispFormat ~= "inside") then
                local frame = form == "bar" and self.frame.BarIconStack or self.frame.IconButtonStack;
                if self.stack and self.stack > 1 then
                    frame:SetText(self.stack);
                else
                    frame:SetText("");
                end
            end
            
            self.frame.BarIconTexture:SetTexture(self.texture);
            self.frame.IconButtonTexture:SetTexture(self.texture);
        end
        
        return needsResize;
    end,
    
    UpdateCachedSettings = function(self)
        self.ghostLength = self:Get("ghostLength");
        self.ghosts = self:Get("ghosts");
        self.fadeOutLength = self:Get("fadeOutLength");
        self.fadeInLength = self:Get("fadeInLength");
        self.timerFormat = self:Get("timerFormat");
        self.iconDisplayFormat = self:Get("iconDisplayFormat");
        self.redPoint = self:Get("redPoint");
        self.soundAlert = self:Get("soundAlert");
        self.barAbsScale = self:Get("barAbsScale");
        self.barScaling = self:Get("barScaling");
        self.barGrow = self:Get("barGrow");
        self.barOrientation = self:Get("barOrientation");
        self.barReversed = self:Get("barReversed");
        self.barMajorLength = self:Get("barMajorLength");
        self.scale = self:Get("scale");
        self.iconUseBG = self:Get("iconUseBG");
    end,
    
    UpdateData = function(self)
        self.name = self.data.name or "Timer";
        self.time = self.data.time or GetTime();
        local oldDur = self.duration;
        self.duration = self.data.duration; --can be nil
        if self.group then
            if self.duration then
                local remaining = self.duration - GetTime() + self.time;
                if remaining > self:Get("redPoint") then
                    self.playedSound = false;
                end
                if self:Get("iconShowCooldown") then
                    self.frame.IconButtonCooldown:SetCooldown(self.time, self.duration);
                end
            end
            if oldDur and not self.duration then
                self:DrawFrame();
            end
        end
        self.stack = self.data.stack; --can be nil
        self.texture = self.data.texture or "Interface\\Icons\\Spell_Lightning_LightningBolt01";
        self.changingColor = true;
    end,
    
    Get = function(self, ...)
        local val;
        if self.settings then
            local checked = TimerLib:AcquireTable();
            local ref = self.settings;
            while ref and val == nil and not checked[ref] do
                val = TimerLib:Get("timerSettings", ref, ...);
                checked[ref] = true;
                ref = TimerLib:Get("timerSettings", ref, "reference");
            end
            TimerLib:ReleaseTable(checked);
        end
        if val == nil then
            local default = self.group.anchor:Get("defaultTimerSetting");
            if default then
                local checked = TimerLib:AcquireTable();
                local ref = default;
                while ref and val == nil and not checked[ref] do
                    val = TimerLib:Get("timerSettings", ref, ...);
                    checked[ref] = true;
                    ref = TimerLib:Get("timerSettings", ref, "reference");
                end
                TimerLib:ReleaseTable(checked);
            end
            if val == nil then
                val = TimerLib:Get("timerSettings", "Default Timer", ...);
            end
        end
        return val;
    end,
    
    Remove = function(self)
        self.data = nil;
        self.isGhost = true;
        self.start = GetTime();
        self:UpdateToGhost();
    end,
    
    UpdateToGhost = function(self)
        if not self.playedSound then
            self.playedSound = true;
            if self:Get("soundAlert") then
                PlaySoundFile("Interface\\AddOns\\DoTimer\\Core\\Files\\alert.wav");
            end
        end
        
        if not isJordan or self:Get("ghosts") then
            local form = self:Get("timerFormat");
            local color = self:Get("ghostColor");
            if form == "bar" then
                self:SetBarText();
                self.frame.BarStatusFrontSpark:Hide();
                self.frame.BarStatusFront:SetStatusBarColor(color.r, color.g, color.b);
                self.frame.BarIconBorder:SetVertexColor(color.r, color.g, color.b);
                self.frame.BarStatusFront:SetValue(1);
                self.frame.BarStatusFront:SetAllPoints(true);
            elseif form == "icon" then
                self.frame.IconButtonBorder:SetVertexColor(color.r, color.g, color.b);
                self.frame.IconButtonCooldown:Hide();
                self.frame.IconText:SetText("");
                self.frame.IconButtonText:SetText("");
                local scale = self:Get("scale");
                self.frame.Icon:SetWidth(self.frame.IconButton:GetWidth() * scale);
                self.frame.Icon:SetHeight(self.frame.IconButton:GetHeight() * scale);
                self:Resize();
                self.group:Resize();
            end
        end
    end,
    
    UpdateSettings = function(self)
        self:UpdateCachedSettings();
        self:DrawFrame();
    end,
    
    DrawFrame = function(self)
        self:UpdateCachedSettings();
        local form = self:Get("timerFormat");
        if form == "bar" then
            local barFrame = self.frame.Bar;
            local iconFrame = self.frame.BarIcon;
            local statusFrame = self.frame.BarStatus;
            barFrame:Show();
            self.frame.Icon:Hide();
            self.frame.mainFrame = barFrame;
            self.frame.BarIconStack:SetText("");
            barFrame:SetScale(self:Get("scale"));
            self.frame.BarButton:EnableMouse(self:Get("clickable"));
            local r, g, b = self:GetColor();
            self.frame.BarStatusFront:SetStatusBarColor(r, g, b);
            self.frame.BarStatusFront:SetValue(1);
            self.frame.BarIconBorder:SetVertexColor(r, g, b);
            local alpha = self:Get("alpha");
            self.frame.BarIcon:SetAlpha(alpha);
            self.frame.BarStatusFront:SetAlpha(alpha);
            self.frame.BarStatusBackground:SetAlpha(alpha / 2);
            local texture = sharedMedia and sharedMedia:Fetch("statusbar", self:Get("barTexture"), 1) or "Interface\\AddOns\\DoTimer\\Core\\Files\\" .. self:Get("barTexture");
            self.frame.BarStatusFront:SetStatusBarTexture(texture);
            self.frame.BarStatusBackground:SetStatusBarTexture(texture);
            local color = self:Get("barBackgroundColor");
            self.frame.BarStatusBackground:SetStatusBarColor(color.r, color.g, color.b);
            
            local fontHeight = self:Get("barFontHeight");
            if fontHeight <= self:Get("barMinorLength") then
                local mid;
                local leftFavored = false;
                local textFormat = self:Get("barTextFormat");
                if match(textFormat, "&") then
                    self.leftText, mid, self.rightText = match(textFormat, "(.-)(:?&:?)(.*)");
                    leftFavored = (mid == ":&");
                else
                    self.leftText = textFormat;
                    self.rightText = "";
                end
            else
                self.leftText = "";
                self.rightText = "";
            end
            self.updateLeftText = string.match(self.leftText, "%%d");
            self.updateRightText = string.match(self.rightText, "%%d");
            local left = self.frame.BarStatusText;
            local right = self.frame.BarStatusTextRight;
            local font = left:GetFont();
            left:SetFont(font, fontHeight);
            right:SetFont(font, fontHeight);
            local color = self:Get("barTextColor");
            left:SetTextColor(color.r, color.g, color.b);
            right:SetTextColor(color.r, color.g, color.b);
            left:ClearAllPoints();
            right:ClearAllPoints();
            left:SetPoint("TOPLEFT", statusFrame, "TOPLEFT", 3, 0);
            left:SetPoint("BOTTOMLEFT", statusFrame, "BOTTOMLEFT", 3, 1);
            right:SetPoint("TOPRIGHT", statusFrame, "TOPRIGHT", -3, 0);
            right:SetPoint("BOTTOMRIGHT", statusFrame, "BOTTOMRIGHT", -3, 1);
            if leftFavored then
                right:SetPoint("LEFT", left, "RIGHT");
            else
                left:SetPoint("RIGHT", right, "LEFT");
            end
            self:SetBarText();
            local barWidth, barHeight = self:Get("barMajorLength"), self:Get("barMinorLength");
            local orient = self:Get("barOrientation");
            if orient == "vertical" then
                barWidth, barHeight = barHeight, barWidth;
            end
            if self:Get("barReversed") then
                local f = self.frame.BarStatusFront;
                f:SetAllPoints(false);
                f:ClearAllPoints();
                f:SetWidth(barWidth);
                f:SetHeight(barHeight);
                if orient == "horizontal" then
                    f:SetPoint("LEFT", self.frame.BarStatus, "LEFT");
                else
                    f:SetPoint("BOTTOM", self.frame.BarStatus, "BOTTOM");
                end
            else
                self.frame.BarStatusFront:SetAllPoints(true);
            end
            
            self.frame.BarStatusFront:SetOrientation(orient);
            statusFrame:ClearAllPoints();
            if self:Get("barShowIcon") then
                iconFrame:Show();
                iconFrame:ClearAllPoints();
                local flip = self:Get("barFlipIcon");
                local pos;
                if orient == "horizontal" then
                    pos = flip and "right" or "left";
                else
                    pos = flip and "top" or "bottom";
                end
                local iRef, sRef, iW, iH, bW, bH;
                if pos == "left" then
                    iRef, sRef = "LEFT", "RIGHT";
                    iW, iH = barHeight, barHeight;
                    bW, bH = barWidth + barHeight, barHeight;
                elseif pos == "right" then
                    iRef, sRef = "RIGHT", "LEFT";
                    iW, iH = barHeight, barHeight;
                    bW, bH = barWidth + barHeight, barHeight;
                elseif pos == "top" then
                    iRef, sRef = "TOP", "BOTTOM";
                    iW, iH = barWidth, barWidth;
                    bW, bH = barWidth, barWidth + barHeight;
                elseif pos == "bottom" then
                    iRef, sRef = "BOTTOM", "TOP";
                    iW, iH = barWidth, barWidth;
                    bW, bH = barWidth, barWidth + barHeight;
                end
                
                iconFrame:SetPoint(iRef, barFrame, iRef);
                statusFrame:SetPoint(iRef, iconFrame, sRef);
                iconFrame:SetWidth(iW - 1);
                iconFrame:SetHeight(iH - 1);
                local border = self.frame.BarIconBorder;
                border:SetWidth(iW);
                border:SetHeight(iH);
                barFrame:SetWidth(bW);
                barFrame:SetHeight(bH);
            else
                iconFrame:Hide();
                statusFrame:SetPoint("TOPLEFT", self.frame.Bar, "TOPLEFT");
                barFrame:SetWidth(barWidth);
                barFrame:SetHeight(barHeight);
            end
            
            statusFrame:SetWidth(barWidth);
            statusFrame:SetHeight(barHeight);
            self.frame.BarStatusFrontSpark:SetHeight(barHeight * 2);
            if orient == "horizontal" and self.duration then
                self.frame.BarStatusFrontSpark:Show();
            else
                self.frame.BarStatusFrontSpark:Hide();
            end
        elseif form == "icon" then
            local iconFrame = self.frame.Icon;
            iconFrame:Show();
            self.frame.Bar:Hide();
            self.frame.mainFrame = iconFrame;
            self.frame.IconButtonStack:SetText("");
            local font = self.frame.IconButtonStack:GetFont();
            self.frame.IconButtonStack:SetFont(font, 10, "OUTLINE");
            local buttonFrame = self.frame.IconButton;
            local scale = self:Get("scale");
            buttonFrame:SetScale(scale);
            buttonFrame:SetAlpha(self:Get("alpha"));
            buttonFrame:EnableMouse(self:Get("clickable"));
            local r, g, b = self:GetColor();
            if self:Get("iconUseBG") then
                local color = self:Get("barBackgroundColor");
                self.frame.IconButtonBorder:SetVertexColor(color.r, color.g, color.b);
            else
                self.frame.IconButtonBorder:SetVertexColor(r, g, b);
            end
            self.frame.IconButtonText:SetTextColor(r, g, b);
            self.frame.IconText:SetTextColor(r, g, b);
            local display = self:Get("iconDisplayFormat");
            local cooldownFrame = self.frame.IconButtonCooldown;
            local outterText = self.frame.IconText;
            local innerText = self.frame.IconButtonText;
            self.text = nil;
            if self:Get("iconShowCooldown") and self.duration then
                cooldownFrame:Show();
                cooldownFrame:SetCooldown(self.time, self.duration);
                cooldownFrame:SetReverse(self:Get("iconReverseCooldown"));
            else
                cooldownFrame:Hide();
            end
            if display == "none" then
                outterText:Hide();
                innerText:Hide();
                buttonFrame:ClearAllPoints();
                buttonFrame:SetPoint("TOPLEFT", iconFrame, "TOPLEFT");
                iconFrame:SetWidth(buttonFrame:GetWidth() * scale);
                iconFrame:SetHeight(buttonFrame:GetHeight() * scale);
            elseif display == "inside" then
                outterText:Hide();
                innerText:Show();
                local font = innerText:GetFont();
                local fontHeight = self:Get("iconFontHeight");
                innerText:SetFont(font, fontHeight, "OUTLINE");
                innerText:ClearAllPoints();
                innerText:SetPoint("CENTER", buttonFrame, "CENTER");
                innerText:SetText("");
                buttonFrame:ClearAllPoints();
                buttonFrame:SetPoint("TOPLEFT", iconFrame, "TOPLEFT");
                iconFrame:SetWidth(buttonFrame:GetWidth() * scale);
                iconFrame:SetHeight(buttonFrame:GetHeight() * scale);
            elseif display == "outside" then
                innerText:Hide();
                outterText:Show();
                outterText:SetText(" ");
                local font = outterText:GetFont();
                local fontHeight = self:Get("iconFontHeight");
                outterText:SetFont(font, fontHeight);
                local dir = self.group:Get("timerDirection");
                local flip = self:Get("iconFlipText");
                buttonFrame:ClearAllPoints();
                outterText:ClearAllPoints();
                if self.duration then
                    if dir == "left" or dir == "right" then
                        self.textPos = "v";
                        if flip then --top
                            buttonFrame:SetPoint("BOTTOMLEFT", iconFrame, "BOTTOMLEFT");
                            outterText:SetPoint("BOTTOM", buttonFrame, "TOP", 0, 3);
                        else --bottom
                            buttonFrame:SetPoint("TOPLEFT", iconFrame, "TOPLEFT");
                            outterText:SetPoint("TOP", buttonFrame, "BOTTOM", 0, -3);
                        end
                        iconFrame:SetHeight((buttonFrame:GetHeight() * scale) + 3 + outterText:GetHeight());
                        iconFrame:SetWidth(math.max(buttonFrame:GetWidth() * scale, outterText:GetWidth()));
                    else
                        self.textPos = "h";
                        if flip then --left
                            buttonFrame:SetPoint("TOPRIGHT", iconFrame, "TOPRIGHT");
                            outterText:SetPoint("RIGHT", buttonFrame, "LEFT", -3, 0);
                        else --right
                            buttonFrame:SetPoint("TOPLEFT", iconFrame, "TOPLEFT");
                            outterText:SetPoint("LEFT", buttonFrame, "RIGHT", 3, 0);
                        end
                        iconFrame:SetHeight(math.max(buttonFrame:GetHeight() * scale, outterText:GetHeight()));
                        iconFrame:SetWidth((buttonFrame:GetWidth() * scale) + 3 + outterText:GetWidth());
                    end
                else
                    outterText:Hide();
                    buttonFrame:SetPoint("TOPLEFT", iconFrame, "TOPLEFT");
                    iconFrame:SetWidth(buttonFrame:GetWidth() * scale);
                    iconFrame:SetHeight(buttonFrame:GetHeight() * scale);
                end
            end
        end
        
        if self.isGhost then
            self:UpdateToGhost();
        end
        
        self:Update();
        self:Resize();
    end,
    
    Resize = function(self)
        local f = self.frame.mainFrame;
        self.frame:SetWidth(f:GetWidth() * f:GetScale());
        self.frame:SetHeight(f:GetHeight() * f:GetScale());
    end,
    
    SetBarText = function(self, time)
        local left = self.frame.BarStatusText;
        local right = self.frame.BarStatusTextRight;
        if self:Get("barOrientation") == "horizontal" then
            if self.duration and not self.isGhost then
                time = time or GetTime();
                subEntries.s = self.name;
                subEntries.d = self:FormatTime(self.duration - time + self.time);
                subEntries.t = self.group.name;
                local leftText = gsub(self.leftText, "%%(%a)", subFunc);
                local rightText = gsub(self.rightText, "%%(%a)", subFunc);
                left:SetText(leftText);
                right:SetText(rightText);
            else
                local leftText = match(self.leftText, "%%s") and self.name or "";
                local rightText = match(self.rightText, "%%s") and self.name or "";
                left:SetText(leftText);
                right:SetText(rightText);
            end
        else
            left:SetText("");
            right:SetText("");
        end
    end,
    
    FormatTime = function(self, number)
        number = max(number, 0);
        local form = self:Get("timeFormat");
        local numHours = number / 3600;
        local numMinutes = fmod(number, 3600);
        local numSeconds = fmod(numMinutes, 60);
        numMinutes = numMinutes / 60;
        
        if form == "letters" then
            if numHours >= 1 then
                return format("%dh", numHours + 1);
            elseif numMinutes >= 1 then
                return format("%dm", numMinutes + 1);
            else
                local str = (self:Get("tenths") and number < 9.95) and "%.1fs" or "%ds";
                return format(str, numSeconds);
            end
        elseif form == "digital" then
            local str = (self:Get("tenths") and number < 9.95) and ":%.2d.%d" or ":%.2d";
            if numHours >= 1 then
                return format("%d:%d" .. str, numHours, numMinutes, numSeconds);
            elseif numMinutes >= 1 then
                return format("%d" .. str, numMinutes, numSeconds);
            else
                return format("0" .. str, numSeconds, 10 * fmod(numSeconds, 1));
            end
        end
    end,
    
    GetColor = function(self)
        if self.duration then
            local redPoint = self:Get("redPoint");
            local remaining = self.duration - GetTime() + self.time;
            local halfway = self.duration / 2;
            if (remaining <= redPoint) then
                local color = self:Get("finalColor");
                return color.r, color.g, color.b;
            elseif (remaining <= redPoint + 2) then
                local old = (remaining <= halfway) and self:Get("middleColor") or self:Get("startColor");
                local new = self:Get("finalColor");
                local fraction = 1 - ((remaining - redPoint) / 2);
                return old.r + ((new.r - old.r) * fraction), old.g + ((new.g - old.g) * fraction), old.b + ((new.b - old.b) * fraction);
            elseif (remaining <= halfway) then
                local color = self:Get("middleColor");
                return color.r, color.g, color.b;
            elseif (remaining <= halfway + 2) then
                local old = self:Get("startColor");
                local new = self:Get("middleColor");
                local fraction = 1 - ((remaining - halfway) / 2);
                return old.r + ((new.r - old.r) * fraction), old.g + ((new.g - old.g) * fraction), old.b + ((new.b - old.b) * fraction);
            else
                local color = self:Get("startColor");
                return color.r, color.g, color.b;
            end
        else
            local color = self:Get("startColor");
            return color.r, color.g, color.b;
        end
    end,
    
    Destruct = function(self)
        table.insert(cachedTables, self);
        activeTables[self] = nil;
        self.frame:Hide();
        self.frame:SetScript("OnUpdate", nil);
        local anchor = self.group.anchor;
        local frame = anchor.frame.anchorFrame;
        if self.isMoving and frame:IsMovable() then
            frame:StopMovingOrSizing();
            local x, y = frame:GetLeft(), frame:GetTop();
            anchor:Set("positionX", x);
            anchor:Set("positionY", y);
            self.isMoving = false;
        end
    end,
}

activeTables = {};
function TimerLibTimer:IsActiveTimer(timer)
    return activeTables[timer] or false;
end

local counter = 0;
function acquireTimerFrame()
    counter = counter + 1;
    local frame = CreateFrame("Frame", "TimerLibTimer" .. counter, nil, "TimerLibTimerFrameTemplate");
    
    local findFrameElement = TimerLib.findFrameElement;
    findFrameElement(frame, "Bar");
    findFrameElement(frame, "BarButton");
    findFrameElement(frame, "BarStatus");
    findFrameElement(frame, "BarStatusText");
    findFrameElement(frame, "BarStatusTextRight");
    findFrameElement(frame, "BarIcon");
    findFrameElement(frame, "BarIconTexture");
    findFrameElement(frame, "BarIconBorder");
    findFrameElement(frame, "BarIconStack");
    findFrameElement(frame, "BarStatusFront");
    findFrameElement(frame, "BarStatusBackground");
    findFrameElement(frame, "BarStatusFrontSpark");
    
    findFrameElement(frame, "Icon");
    findFrameElement(frame, "IconText");
    findFrameElement(frame, "IconButton");
    findFrameElement(frame, "IconButtonText");
    findFrameElement(frame, "IconButtonTexture");
    findFrameElement(frame, "IconButtonCooldown");
    findFrameElement(frame, "IconButtonStack");
    findFrameElement(frame, "IconButtonBorder");
    
    --[[
    local t = frame.Icon:CreateTexture();
    t:SetAllPoints(true);
    t:SetTexture(1, 0, 1, .5);--]]
    
    return frame;
end

cachedTables = {};
function TimerLibTimer:NewTimer(module, timerdata, settings, priority, hidden)
    local timer = table.remove(cachedTables, 1);
    if timer then
        module:ClearTable(timer.state);
        timer.frame:Show();
    else
        timer = module:AcquireTable();
        timer.state = module:AcquireTable();
        timer.frame = acquireTimerFrame();
        timer.frame.timer = timer;
        timer.frame.BarButton.timer = timer;
        timer.frame.IconButton.timer = timer;
        for i, v in pairs(timerFuncs) do
            timer[i] = v;
        end
    end
    
    timer.priority = priority;
    timer.settings = settings;
    timer.hidden = hidden;
    timer.module = module;
    timer.data = timerdata;
    timer.start = GetTime();
    setmetatable(timer, TimerLib.stateMeta);
    timer:UpdateData();
    activeTables[timer] = true;
    return timer;
end
